/**
 * 
 */
package org.vsg.stock.core.indicator.kline;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vsg.stock.core.KLineStock;
import org.vsg.stock.core.KLineStock.Attribute;
import org.vsg.stock.core.domain.StockDailyKLine;
import org.vsg.stock.core.kline.DailyKLineStock;

/**
 * @author Bill Vison
 *
 */
public class DefaultTrendingCycleBuilder extends TrendingCycleBuilder {
	
	
	private static final Logger logger = LoggerFactory.getLogger(DefaultTrendingCycleBuilder.class);	
	
	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.TrendingCycleBuilder#buildUp()
	 */
	@Override
	void buildUp() {
		
		// --- get the highest and lowest in this cycle index----
		int highIndex = 0;
		int lowIndex = 0;
		
		double lastHighPrice = 0;
		double lastLowPrice = 99999999;
		DailyKLineStock dailyKLine = null;
		List<Date> dateIndexes = new LinkedList<Date>();
		for (int i = 0 ; i < handleDailyKLineList.size() ;i++) {
			dailyKLine = handleDailyKLineList.get(i);
			if (dailyKLine.getHigh() > lastHighPrice) {
				lastHighPrice = dailyKLine.getHigh();
				highIndex = i;
			}
			if (dailyKLine.getLow() < lastLowPrice) {
				lastLowPrice = dailyKLine.getLow();
				lowIndex = i;
			}
			dateIndexes.add( dailyKLine.getDate() );
		}
		int startIndex = highIndex < lowIndex ? highIndex : lowIndex;
		int secIndex = highIndex < lowIndex ? lowIndex : highIndex;
		

		// --- find the zig item ---
		DefaultZigBuilder zigBuilder = new DefaultZigBuilder();
		zigBuilder.setDailyKLineStockList( handleDailyKLineList );
		zigBuilder.setThreshold( (double)4.3 );
		zigBuilder.setKLineStockAttribute( KLineStock.Attribute.CLOSE  );
		
		zigBuilder.buildUp();
		
		List<ZigIndicatorResultItem> allResultItems = null;
		try {
			
			allResultItems = zigBuilder.getResultItems( ZigBuilder.ALL );
			StockDailyKLine sdk = null;
			Date startDate = null;
			Date endDate = null;
			Date comparedDate = null;

			
			// --- mark the counter for every zig item cycle ---
			
			Vector<SelectedCycle> indexScMarker = new Vector<SelectedCycle>();
			int markChangingIndex = 0;
			
			// --- new program ---
			LinkedList<Integer> indexesOfSeparator = new LinkedList<Integer>();
			// --- init first value ---
			indexesOfSeparator.add(startIndex);
			indexesOfSeparator.add(secIndex);
			indexesOfSeparator.add( handleDailyKLineList.size() - 1);			

			int beginIndexInThisCycle = 0;
			int indexOfItemsForSearching = 0;

			do {
				boolean breakStopMarked = false;
				ZigIndicatorResultItem lastCrest = null;
				ZigIndicatorResultItem lastTrough = null;
				
				// ---stock this handle
				int endIndexInThisCycle = indexesOfSeparator.peek();

				// --- get the start date and the end date 
				startDate =  handleDailyKLineList.get(beginIndexInThisCycle).getDate();
				endDate = handleDailyKLineList.get(endIndexInThisCycle).getDate();
				TrendingCycle.Trending trendingThisCycle = gressFirstTrendingThisCycle(beginIndexInThisCycle , endIndexInThisCycle);
				
				if (logger.isDebugEnabled()) {
					logger.info("start and end date : " + sdf.format(startDate) + " , " + sdf.format(endDate) );
					logger.info("trending cycle : " + trendingThisCycle);
				}				

				int counterByContinueChaning = 0;
				
				for (int index = indexOfItemsForSearching ; index < allResultItems.size() ; index++) {
					ZigIndicatorResultItem item = allResultItems.get(index);
					sdk = (StockDailyKLine)item.getAssoRef();
					comparedDate = sdk.getDate();
					
					if ( logger.isDebugEnabled()) {
						String dateStr = sdf.format( comparedDate );
						System.out.println("start and end date : " + sdf.format(startDate) + " , " + sdf.format(endDate));
						System.out.println("comparedDate : " + dateStr);
						if ( dateStr.equals("2010-02-02")) {
							System.out.println("debug begin : ");
						}


					}
					

					// --- new program ---
					if (comparedDate.compareTo(startDate)>=0 && comparedDate.before(endDate)) {
						
						// --- process program by different way ---
						if (trendingThisCycle == TrendingCycle.Trending.DOWNWARD) {
						
							if (item.getType() == ZigIndicatorResultItem.Type.TROUGH) {

								if (lastTrough != null) {
									
									boolean troughResult = checkTroughLowerIncreasingly(item , lastTrough );
									if ( !troughResult) {
										if ( counterByContinueChaning == 0) {
											markChangingIndex = dateIndexes.indexOf( comparedDate );
										}
										// --- mark this message ---
										counterByContinueChaning+=1;
										
										// --- fire the event ---
										if (counterByContinueChaning > 1) {

											// --- record new cycle ---
											int[] actualIndexes = getActualEndIndexes(beginIndexInThisCycle , markChangingIndex);
											// --- add the index by separator ---
											breakStopMarked = true;

											
											
											endIndexInThisCycle = actualIndexes[1];
											indexOfItemsForSearching = index - 5;
											break;
										}										
									} else {
										if ( counterByContinueChaning < 3 && counterByContinueChaning > 0) {
											counterByContinueChaning = 0;
										}
									}
								}
								lastTrough = item;
							}							
						}
						
						else if (trendingThisCycle == TrendingCycle.Trending.UPWARD) {
							
							if (item.getType() == ZigIndicatorResultItem.Type.CREST) {

								if ( lastCrest != null) {
									boolean crestResult = checkCrestHigherIncreasingly(item , lastCrest );
									

									// --- changing trending running --
									if ( !crestResult ) {
										if ( counterByContinueChaning == 0) {
											markChangingIndex = dateIndexes.indexOf( comparedDate );
										}
										// --- mark this message ---
										counterByContinueChaning+=1;
										
										// --- fire the event ---
										if (counterByContinueChaning > 1) {

											// --- record new cycle ---
											int[] actualIndexes = getActualEndIndexes(beginIndexInThisCycle , markChangingIndex);
											// --- add the index by separator ---
											breakStopMarked = true;
											endIndexInThisCycle = actualIndexes[1];
											indexOfItemsForSearching = index - 5;
											break;
										}											
										
									} else {
										if ( counterByContinueChaning < 3 && counterByContinueChaning > 0) {
											counterByContinueChaning = 0;
										}										
									}
								}
								lastCrest = item;								
							}

						}

					}
					
					// --- stop the looping ---
					if ( comparedDate.after(endDate)) {
						indexOfItemsForSearching = index - 1;
						break;
					}
					
				} 

				// --- this code for index cycle ---
				if (endIndexInThisCycle == this.handleDailyKLineList.size() - 1) {
					int[] actualIndexesThisCycle = getActualEndIndexes(beginIndexInThisCycle , endIndexInThisCycle);

					if ( actualIndexesThisCycle[1] < endIndexInThisCycle && Math.abs( actualIndexesThisCycle[1] - endIndexInThisCycle ) > 3) {
						endIndexInThisCycle = actualIndexesThisCycle[1];
						indexOfItemsForSearching = allResultItems.size() - 1;
						breakStopMarked = true;
					} else {
						if (trendingThisCycle == TrendingCycle.Trending.UPWARD) {
							trendingThisCycle = TrendingCycle.Trending.ESTIMATED_UPWARD;
						} else if ( trendingThisCycle == TrendingCycle.Trending.DOWNWARD ) {
							trendingThisCycle = TrendingCycle.Trending.ESTIMATED_DOWNWARD;
						}						
					}
					
				}
				
				
				// ---- build the new selection cycle ---
				SelectedCycle sc = new SelectedCycle();
				sc.setStartIndex( beginIndexInThisCycle );
				sc.setEndIndex( endIndexInThisCycle );
				sc.setTrending( trendingThisCycle );
				indexScMarker.add( sc );
				
				if ( logger.isInfoEnabled() ) {
					Date sDate = handleDailyKLineList.get(sc.getStartIndex()).getDate();
					Date eDate = handleDailyKLineList.get(sc.getEndIndex()).getDate();
					
					logger.info( sdf.format(sDate) + " , " + sdf.format(eDate) + " , " + sc.getTrending() );
				}
				
				// --- remark the last date ---
				beginIndexInThisCycle = endIndexInThisCycle;
				// --- remove first element
				if (!breakStopMarked) {
					indexesOfSeparator.removeFirst();
				}
				
				
			} while (!indexesOfSeparator.isEmpty());			
			
			
			
			// --- convert to trending cycle ---
			selectedTrendingCycleList = convertToTrendCycleFormSC(indexScMarker);
			
		} catch (IndicatorBuilderNotExcutingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	

	
	private boolean checkTroughLowerIncreasingly(ZigIndicatorResultItem currentLevel , ZigIndicatorResultItem lastLevel) {
		// --- compared between current value and last level ----
		double currentLevelValue = ( (StockDailyKLine)currentLevel.getAssoRef() ).getLow();
		double lastLvelValue = ( (StockDailyKLine)lastLevel.getAssoRef() ).getLow();
		
		return currentLevelValue <= lastLvelValue;
		
	}

	private boolean checkCrestHigherIncreasingly(ZigIndicatorResultItem currentLevel , ZigIndicatorResultItem lastLevel) {
		double currentLevelHigh = ( (StockDailyKLine)currentLevel.getAssoRef() ).getHigh();
		double lastLvelHigh = ( (StockDailyKLine)lastLevel.getAssoRef() ).getHigh();
		
		return currentLevelHigh >= lastLvelHigh;
	}

	private TrendingCycle.Trending gressFirstTrendingThisCycle(int startIndex , int endIndex) {

		int highIndex = 0;
		int lowIndex = 0;
		
		double lastHighPrice = 0;
		double lastLowPrice = 99999999;
		DailyKLineStock dailyKLine = null;
		for (int i = startIndex ; i < endIndex ;i++) {
			dailyKLine = this.handleDailyKLineList.get(i);
			if (dailyKLine.getHigh() > lastHighPrice) {
				lastHighPrice = dailyKLine.getHigh();
				highIndex = i;
			}
			if (dailyKLine.getLow() < lastLowPrice) {
				lastLowPrice = dailyKLine.getLow();
				lowIndex = i;
			}
		}
		
		DailyKLineStock dailyOfLow = handleDailyKLineList.get(lowIndex);
		DailyKLineStock dailyOfHigh = handleDailyKLineList.get(highIndex);
		DailyKLineStock dailyOfStart = handleDailyKLineList.get( startIndex );
		
		int lowDistance = Math.abs( lowIndex - startIndex);
		int highDistance = Math.abs( highIndex - startIndex );
		
		TrendingCycle.Trending result = null;
		if (lowIndex < highIndex) {

			if (dailyOfStart.getLow() > dailyOfLow.getLow() && lowDistance > 10) {
				result = TrendingCycle.Trending.DOWNWARD;
			} else {
				result = TrendingCycle.Trending.UPWARD;				
			}
			
		} else {
			if (dailyOfStart.getHigh() < dailyOfHigh.getHigh() && highDistance > 10) {
				result = TrendingCycle.Trending.UPWARD;
			} else {
				result = TrendingCycle.Trending.DOWNWARD;
			}
		}
		return result;
	}
	
	/**
	 * the first element in array is value of start index , ths last element in array is value of end index
	 * @param subStartIndex
	 * @param subEndIndex
	 * @return
	 */
	private int[] getActualEndIndexes(int subStartIndex , int subEndIndex) {
		double lastHighPrice = 0;
		double lastLowPrice = 99999999;
		DailyKLineStock dailyKLine = null;

		int highIndex = 0;
		int lowIndex = 0;
		for (int i = subStartIndex ; i < subEndIndex ; i++) {
			dailyKLine = this.handleDailyKLineList.get(i);
			if (dailyKLine.getHigh() > lastHighPrice) {
				lastHighPrice = dailyKLine.getHigh();
				highIndex = i;
			}
			if (dailyKLine.getLow() < lastLowPrice) {
				lastLowPrice = dailyKLine.getLow();
				lowIndex = i;
			}			
		}
		int startIndex = highIndex < lowIndex ? highIndex : lowIndex;
		int endIndex = highIndex < lowIndex ? lowIndex : highIndex;
		
		int[] indexes = new int[2];
		indexes[0] = startIndex;
		indexes[1] = endIndex;
		
		return indexes;
		
	}

	
	private List<DailyKLineStock> handleDailyKLineList ;
	
	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.TrendingCycleBuilder#setDailyKLineStockList(java.util.List)
	 */
	@Override
	void setDailyKLineStockList(List<DailyKLineStock> importedList) {
		// TODO Auto-generated method stub
		handleDailyKLineList = importedList;
	}
	
	private Attribute attribute;
	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.TrendingCycleBuilder#setKLineStockAttribute(org.vsg.stock.core.KLineStock.Attribute)
	 */
	@Override
	void setKLineStockAttribute(Attribute stockAttribute) {
		
		this.attribute = stockAttribute;
	}
	
	private List<TrendingCycle> selectedTrendingCycleList;
	
	private List<TrendingCycle> convertToTrendCycleFormSC(List<SelectedCycle>  scList) {
		
		SelectedCycle selCycle = null;
		DefaultTrendingCycle dtc = null;
		
		Vector<TrendingCycle> result = new Vector<TrendingCycle>();
		
		for (int i = 0 ;i < scList.size() ;i++) {
			selCycle = scList.get(i);
			
			dtc = new DefaultTrendingCycle();
			dtc.setTrending( selCycle.getTrending() );
			
			Vector<DailyKLineStock> dailyKLineStkVec = new Vector<DailyKLineStock>();
			
			for (int j = selCycle.getStartIndex() ; j <= selCycle.getEndIndex() ; j++) {
				dailyKLineStkVec.add( handleDailyKLineList.get( j ) );
			}
			dtc.setSelectedCycleList( dailyKLineStkVec );

			result.add( dtc );
		}
		
		return result;
	}
	
	@Override
	public List<TrendingCycle> getTrendingCycleList() {
		// TODO Auto-generated method stub
		return selectedTrendingCycleList;
	}
	
	private class SelectedCycle {
		
		private int startIndex;
		
		private int endIndex;
		
		private TrendingCycle.Trending trending;

		public int getStartIndex() {
			return startIndex;
		}

		public void setStartIndex(int startIndex) {
			this.startIndex = startIndex;
		}

		public int getEndIndex() {
			return endIndex;
		}

		public void setEndIndex(int endIndex) {
			this.endIndex = endIndex;
		}

		public TrendingCycle.Trending getTrending() {
			return trending;
		}

		public void setTrending(TrendingCycle.Trending trending) {
			this.trending = trending;
		}

		@Override
		public String toString() {
			return "SelectedCycle [startIndex=" + startIndex + ", endIndex="
					+ endIndex + ", trending=" + trending + "]";
		}

		
	}

	
	
}
